#include "stdafx.h"

#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <math.h>
#include "cairo/cairo-win32.h"
#include <crtdbg.h>
#include <process.h>
#include <MMSystem.h>

#include "config.h"
#include "debug.h"
#include "multiscaleimage.h"
#include "multiscaletilesource.h"
#include "deepzoomimagetilesource.h"
#include "multiscalesubimage.h"
#include "bitmapimage.h"
#include "MultiScaleSubImageCollection.h"
#include "region.h"
#include "surface-cairo.h"
#include "context-cairo.h"

#include "WinThreadsPool.h"

#define CHECK_MEM_LEAK

#ifdef CHECK_MEM_LEAK
	#define _CRTDBG_MAP_ALLOC
	#include <stdlib.h>
	#include <crtdbg.h>

	#define DEBUG_NEW new( _NORMAL_BLOCK, __FILE__, __LINE__ )
	#define new DEBUG_NEW
#endif

#pragma comment ( lib, "winmm.lib" )

namespace DeepZoomImage
{
	//#ifdef LOGGING
	//#include "clock.h"
	//#define MSI_STARTTIMER(id)    if (G_UNLIKELY (debug_flags & RUNTIME_DEBUG_MSI)) TimeSpan id##_t_start = get_now()
	//#define MSI_ENDTIMER(id,str)  if (G_UNLIKELY (debug_flags & RUNTIME_DEBUG_MSI)) TimeSpan id##_t_end = get_now(); printf ("timing of '%s' took %f ms\n", str, id##_t_end, (double)(id##_t_end - id##_t_start) / 10000)
	//#else
	#define MSI_STARTTIMER(id)
	#define MSI_ENDTIMER(id,str)
	//#endif

	#define pow2TableMax (30)
	static guint64 pow2Table[pow2TableMax][2] = { {0, 1}, {1, 2}, {2, 4}, {3, 8}, { 4, 16}, { 5, 32}, { 6, 64}, { 7, 128}, { 8, 256}, { 9, 512}, 
	{ 10, 1024}, { 11, 2048}, { 12, 4096},	{ 13, 8192}, { 14, 16384}, { 15, 32768},{ 16, 65536}, { 17, 131072}, { 18, 262144}, { 19, 524288}, 
	{ 20, 1048576}, { 21, 2097152}, { 22, 4194304}, { 23, 8388608}, { 24, 16777216}, { 25, 33554432}, 	{ 26, 67108864}, { 27, 134217728},
	{ 28, 268435456}, { 29, 536870912} };

	static inline guint64 pow2 (int pow)
	{
		if ( pow < pow2TableMax )
		{
			return pow2Table[pow][1];
		}
		else
		{
			return ((guint64) 1 << pow);
		}
	}

	#define blur_factor_is_valid(x) (!isnan (x) && !isinf (x) && (x) > 0.0)

	static inline int blur_factor_get_offset (double factor)
	{
		int offset;

		if (frexp (factor, &offset) == 0.5)
			offset--;

		return -offset;
	}

	/*
	* Q(uad)Tree.
	*/

	struct QTreeTile {
		BitmapImage *image;
		double opacity;

		QTreeTile() {
			image = NULL;
			opacity = 1.0;
		}
	};

	struct QTree {
		QTreeTile *tile;
		QTree *parent;
		QTree *l0; //N-E
		QTree *l1; //N-W
		QTree *l2; //S-E
		QTree *l3; //S-W

		QTree() {
			tile = NULL;
			parent = NULL;
			l0 = NULL;
			l1 = NULL;
			l2 = NULL;
			l3 = NULL;
		}
	};

	static QTree * qtree_new (void)
	{
		return g_new0 (QTree, 1);
	}

	static QTree * qtree_insert (QTree *root, int level, guint64 x, guint64 y)
	{
		guint64 level2 = pow2 (level);
		QTree *node = root;

		if (x >= level2 || y >= level2)
			return NULL;

		if (!root)
			return NULL;

		while (node && level-- > 0) {
			level2 = pow2 (level);

			if (y < level2) {
				if (x < level2) {
					if (!node->l0) {
						node->l0 = qtree_new ();
						node->l0->parent = node;
					}
					node = node->l0;
				} else {
					if (!node->l1) {
						node->l1 = qtree_new ();
						node->l1->parent = node;
					}
					node = node->l1;
					x -= level2;
				}
			} else {
				if (x < level2) {
					if (!node->l2) {
						node->l2 = qtree_new ();
						node->l2->parent = node;
					}
					node = node->l2;
					y -= level2;
				} else {
					if (!node->l3) {
						node->l3 = qtree_new ();
						node->l3->parent = node;
					}
					node = node->l3;
					x -= level2;
					y -= level2;
				}
			}
		}

		return node;
	}

	static void qtree_set_tile (QTree *node, BitmapImage *image, double opacity)
	{
		if (image)
			image->ref ();

		if (node->tile) {
			if (node->tile->image)
				node->tile->image->unref ();
		} else
			node->tile = g_new (QTreeTile, 1);

		node->tile->opacity = opacity;
		node->tile->image = image;
	}

	static QTree *qtree_lookup (QTree *root, int level, guint64 x, guint64 y)
	{
		guint64 level2 = pow2 (level);
		QTree *node = root;

		if (x >= level2 || y >= level2)
			return NULL;

		while (node && level-- > 0) {
			level2 = pow2 (level);

			if (y < level2) {
				if (x < level2) {
					node = node->l0;
				} else {
					node = node->l1;
					x -= level2;
				}
			} else {
				if (x < level2) {
					node = node->l2;
					y -= level2;
				} else {
					node = node->l3;
					x -= level2;
					y -= level2;
				}
			}
		}

		return node;
	}

	static QTreeTile * qtree_lookup_tile (QTree *root, int level, guint64 x, guint64 y)
	{
		QTree *node = qtree_lookup (root, level, x, y);
		return node ? node->tile : NULL;
	}

	static void qtree_remove (QTree *node, int depth)
	{
		if (!node)
			return;

		if (node->tile) {
			if (node->tile->image)
				node->tile->image->unref();
			g_free (node->tile);
			node->tile = NULL;
		}

		if (depth <= 0)
			return;

		qtree_remove (node->l0, depth - 1);
		qtree_remove (node->l1, depth - 1);
		qtree_remove (node->l2, depth - 1);
		qtree_remove (node->l3, depth - 1);
	}

	static void qtree_remove_at (QTree *root, int level, guint64 x, guint64 y, int depth)
	{
		QTree *node = qtree_lookup (root, level, x, y);

		qtree_remove (node, depth);
	}

	static inline bool qtree_has_tile (QTree *node)
	{
		return node->tile != NULL;
	}

	static void qtree_destroy (QTree *root)
	{
		if (!root)
			return;

		if (root->tile) {
			if (root->tile->image) {
				bool lastRef = root->tile->image->GetRefCount()==1;
				root->tile->image->unref ();
				if ( lastRef )  {
					root->tile->image = NULL;
				}
			}
			g_free (root->tile);
			root->tile = NULL;
		}

		qtree_destroy (root->l0);
		root->l0 = NULL;
		qtree_destroy (root->l1);
		root->l1 = NULL;
		qtree_destroy (root->l2);
		root->l2 = NULL;
		qtree_destroy (root->l3);
		root->l3 = NULL;
		g_free (root);
		root = NULL;
	}

	/*
	* BitmapImageContext
	*/

	struct BitmapImageContext : public List::Node {
		MultiScaleImage *msi;
		BitmapImage *image;
		QTree *node;
	};

	/*
	* Morton layout
	*/

#if 0
	static void morton (int n, int *x, int *y) {
			n = (n & 0x99999999) + ((n & 0x22222222) << 1) + ((n & 0x44444444) >> 1);
			n = (n & 0xc3c3c3c3) + ((n & 0x0c0c0c0c) << 2) + ((n & 0x30303030) >> 2);
			n = (n & 0xf00ff00f) + ((n & 0x00f000f0) << 4) + ((n & 0x0f000f00) >> 4);
			n = (n & 0xff0000ff) + ((n & 0x0000ff00) << 8) + ((n & 0x00ff0000) >> 8);
			*x = n & 0x0000ffff;
			*y = n >> 16;
	}
#endif

	static inline int morton_x (int n)
	{
		n = (n & 0x11111111) + ((n & 0x44444444) >> 1);
		n = (n & 0x03030303) + ((n & 0x30303030) >> 2);
		n = (n & 0x000f000f) + ((n & 0x0f000f00) >> 4);
		return  (n & 0x000000ff) + ((n & 0x00ff0000) >> 8);
	}

	static inline int morton_y (int n)
	{
		n = (n & 0x88888888) + ((n & 0x22222222) << 1);
		n = (n & 0xc0c0c0c0) + ((n & 0x0c0c0c0c) << 2);
		n = (n & 0xf000f000) + ((n & 0x00f000f0) << 4);
		n = (n & 0xff000000) + ((n & 0x0000ff00) << 8);

		return n >> 16;
	}

	static void int_free (gpointer user_data)
	{
		delete (int *) user_data;
	}

	#define MOTION_IS_FADING   (1 << 0)
	#define MOTION_IS_PANNING  (1 << 1)
	#define MOTION_IS_ZOOMING  (1 << 2)
	#define MOTION_IS_FINISHED (1 << 3)

	#define IS_IN_MOTION(m) (((m) & (MOTION_IS_FADING | MOTION_IS_PANNING | MOTION_IS_ZOOMING)) != 0)

#define MULTI_THREAD_LOAD_TILE

#ifdef MULTI_THREAD_LOAD_TILE
	HANDLE g_hLoadTileEvent = NULL;
	HANDLE g_hLoadTileThread = NULL;
	unsigned int g_LTThreadID = 0;
	CRITICAL_SECTION g_LTLock;
	MultiScaleImage * g_pMSI = NULL;
	bool g_bQuitThread = false;

	unsigned int WINAPI loadTileThread( void * lpParam );
	unsigned int WINAPI dynamicLoadTileThread( void * lpParam );
	void XSleep( DWORD dwDelay, HANDLE hEvent );
#endif

//#define USE_THREADS_POOL

	MultiScaleImage::MultiScaleImage ()
		: m_AspectRatio(1.0)
		, m_BlurFactor(1.0)
		, m_IsIdle(true)
		, m_pMSTSource(NULL)
		, m_pSubImages(NULL)
		, m_UseSprings(false)
		, m_ViewportWidth(1.0)
		, m_TileFade(0.0)
		, m_AnimatedViewportWidth(1.0)
		, m_pContext(NULL)
		, m_pRegion(NULL)
		, m_Width(0.0)
		, m_Height(0.0)
		, m_ActualWidth(1024.0)
		, m_ActualHeight(768.0)
		, m_MaxWidth(INFINITY)
		, m_MaxHeight(INFINITY)
		, m_MinWidth(80.0)
		, m_MinHeight(60.0)
		, m_OriginalPixelWidth(0.0)
		, m_OriginalPixelHeight(0.0)
		, total_opacity(1.0)
		, flags(TOTAL_RENDER_VISIBLE)
		, m_InitLayers(0)
		, m_MaxLayers(0)
		, m_MaxCacheTiles(MAX_CACHE_TILE_QUEUE_SIZE)
		, m_pTarget(NULL)
	{
		// Note: cairo_user_data_key_t's do not need to be initialized
#ifdef LOGGING
		set_debug_option( RUNTIME_DEBUG_MSI, true );
#endif

		m_ViewportOrigin = Point(0, 0);
		m_AnimatedViewportOrigin = Point(0,0);

		// call qtree_destroy( subimage_cache ) in manual. not call auto again.
		cache = g_hash_table_new_full (g_int_hash, g_int_equal, int_free, (GDestroyNotify) NULL ); //qtree_destroy);    // mem leak 
		subimages_sorted = false;
		pan_target = Point (0, 0);
		zoom_target = 1.0;
		motion = 0;

// 		CairoSurface * pTarget = new CairoSurface(m_ActualWidth, m_ActualHeight);
// 		m_pContext = new CairoContext( pTarget );
// 		pTarget->unref();
		m_pRegion = new Region();
		m_pSubImages = new MultiScaleSubImageCollection();

		surface_bounds = Rect (0,0,0,0);
		dirty_region = new Region ();

#ifdef MULTI_THREAD_LOAD_TILE
		if ( NULL == g_hLoadTileEvent ) {
			g_hLoadTileEvent = CreateEvent( NULL, false, false, NULL );
			InitializeCriticalSection( &g_LTLock );
		}
		g_pMSI = this;
		g_bQuitThread = false;
#endif

		m_pCacheTileQueue = new deque<LoadedTile *>;
		m_pCacheTileQueue->clear();
	}

	MultiScaleImage::~MultiScaleImage ()
	{
		// Note that the storyboard may live longer than the MSI, so we must
		// remove the completed handler before we unref it in the DOPtr dtor.
#ifdef USE_ANIMATION
		if (fadein_sb)
			fadein_sb->RemoveHandler (Storyboard::CompletedEvent, fade_finished, this);
		if (zoom_sb)
			zoom_sb->RemoveHandler (Storyboard::CompletedEvent, zoom_finished, this);
		if (pan_sb)
			pan_sb->RemoveHandler (Storyboard::CompletedEvent, pan_finished, this);
#endif

	#ifdef USE_THREADS_POOL
		deleteThreadsPool();
	#endif

	#ifdef MULTI_THREAD_LOAD_TILE
		if ( g_hLoadTileThread != NULL && g_LTThreadID > 0 ) {
			PostThreadMessage( g_LTThreadID, WM_QUIT, 0, 0 );

			EnterCriticalSection( &g_LTLock );
			g_bQuitThread = true;
			LeaveCriticalSection( &g_LTLock );

			int timeOut = 500;
		#ifdef _DEBUG
			timeOut = 5000;
		#endif
			DWORD ret = WaitForSingleObject( g_hLoadTileThread, timeOut );
			if ( ret == WAIT_TIMEOUT )  {
				//TerminateThread( g_hLoadTileThread, exitCode );
			}
			g_hLoadTileThread = NULL;
			g_LTThreadID = 0;
		}
	#endif

		MultiScaleTileSource *source = GetSource ();
		unLoading();

		int index = -1;
		QTree *subimage_cache = (QTree *) g_hash_table_lookup (cache, &index);
		if ( subimage_cache ) {
			qtree_destroy( subimage_cache );
		}

		g_hash_table_destroy (cache);

		if ( m_pContext )
		{
			delete m_pContext;
			m_pContext = NULL;
		}
		if ( m_pRegion )
		{
			delete m_pRegion;
			m_pRegion = NULL;
		}
		if ( m_pSubImages )
		{
			delete m_pSubImages;
			m_pSubImages = NULL;
		}
		if ( m_pTarget )
		{
			//m_pTarget->unref();
			delete m_pTarget;
			m_pTarget = NULL;
		}

		InvalidateBitmapCache();
		delete dirty_region;

		LoadedTile *pTile = NULL;
		for ( int i=0; i<m_pCacheTileQueue->size(); i++ ) {
			pTile = m_pCacheTileQueue->at(i);
			if ( pTile ) {
				delete pTile;
			}
		}
		m_pCacheTileQueue->clear();
		delete m_pCacheTileQueue;
		m_pCacheTileQueue = NULL;

	#ifdef MULTI_THREAD_LOAD_TILE
		if ( g_hLoadTileEvent ) {
			CloseHandle( g_hLoadTileEvent );
			g_hLoadTileEvent = NULL;
			DeleteCriticalSection( &g_LTLock );
		}
	#endif

	}

	void MultiScaleImage::ZoomAboutLogicalPoint (double zoomIncrementFactor, double zoomCenterLogicalX, double zoomCenterLogicalY)
	{
		LOG_MSI ("\nzoomabout logical %f  (%f, %f)\n", zoomIncrementFactor, zoomCenterLogicalX, zoomCenterLogicalY);

		zoomCenterLogicalX = CLAMP( zoomCenterLogicalX, 0.0, 1.0 );
		zoomCenterLogicalY = CLAMP( zoomCenterLogicalY, 0.0, 1.0 );
		
#ifdef USE_ANIMATION
		if (zoom_sb)
			zoom_sb->PauseWithError (NULL);
		if (pan_sb)
			pan_sb->PauseWithError (NULL);
#endif

		double viewport_width = 1.0;
		Point viewport_origin = Point(0.0, 0.0);

#ifdef USE_ANIMATION
		if (GetUseSprings() && zoom_sb && pan_sb) {
#else
		if (GetUseSprings() ) {
#endif
			viewport_width = zoom_target;
			viewport_origin = pan_target;
		} else {
			viewport_width = GetAnimatedViewportWidth();
			viewport_origin = GetAnimatedViewportOrigin();
		}

		double width = viewport_width / zoomIncrementFactor;
		SetViewportWidth (width);
		SetAnimatedViewportWidth( width );

		if (!isnan(zoomCenterLogicalX) && !isnan(zoomCenterLogicalY)) 
		{
			Point pt = Point (zoomCenterLogicalX - (zoomCenterLogicalX - viewport_origin.x) / zoomIncrementFactor,
				zoomCenterLogicalY - (zoomCenterLogicalY - viewport_origin.y) / zoomIncrementFactor);

			SetViewportOrigin ( &pt );
			AnimateViewportOrigin( &pt );
		}
	}

	Point MultiScaleImage::ElementToLogicalPoint (Point elementPoint)
	{
		Point vp_origin = GetAnimatedViewportOrigin();
		double vp_width = GetAnimatedViewportWidth();
		double actual_width = GetActualWidth();
		return Point (vp_origin.x + (double)elementPoint.x * vp_width / actual_width,
			vp_origin.y + (double)elementPoint.y * vp_width / actual_width);
	}

	Point MultiScaleImage::LogicalToElementPoint (Point logicalPoint)
	{
		Point vp_origin = GetAnimatedViewportOrigin ();
		double vp_width = GetAnimatedViewportWidth ();
		double actual_width = GetActualWidth ();
		return Point ((logicalPoint.x - vp_origin.x) * actual_width / vp_width,
			(logicalPoint.y - vp_origin.y) * actual_width / vp_width);
	}

	bool MultiScaleImage::CanLoadMoreTiles()
	{
		return true;
		//return m_loaders.Length () < MAX_DOWNLOADERS;
	}

	void MultiScaleImage::LoadTile (void *tile, void *user_data)
	{
		BitmapImageContext *ctx = NULL;

#ifdef MULTI_THREAD_LOAD_TILE
		EnterCriticalSection( &g_LTLock );
#endif

		// Check that we aren't already downloading this tile
		for (ctx = static_cast<BitmapImageContext *> (m_loaders.First ());
			ctx; ctx = static_cast<BitmapImageContext *> (ctx->next)) {
				if ( ctx->image->GetTileSource () == tile) {
					LOG_MSI ("Tile %s is already being loaded!\n", (char *) tile );
					return;
				}
		}

		//LOG_MSI (" loading tile %s\n", (char *)tile );

		ctx = new BitmapImageContext ();
		ctx->msi = this;
		ctx->image = new BitmapImage();
		ctx->node = (QTree *) user_data;		

		m_loaders.Append(ctx);

		ctx->image->SetTileSource (tile);

		SetIsIdle (false);

#ifdef MULTI_THREAD_LOAD_TILE
		LeaveCriticalSection( &g_LTLock );
#endif
	}

#if 0
	void MultiScaleImage::UnloadTile (BitmapImageContext *ctx)
	{
		//LOG_MSI( "caching tile failed. for %s.\n", (char *)ctx->image->GetTileSource() );
		qtree_set_tile( ctx->node, NULL, 0.0 );
		m_loaders.Unlink(ctx);
		ctx->image->unref();
		delete ctx;
		Invalidate ();
	}
#endif

	// Only used for DeepZoom sources
	void MultiScaleImage::HandleDzParsed ()
	{
		// if the source is a collection, fill the subimages list
		MultiScaleTileSource *source = GetSource ();
		MultiScaleSubImageCollection *subs = GetSubImages ();
		DeepZoomImageTileSource *dzits = NULL;

		if ( !source || !subs )
		{
			return;
		}

		if (source->GetImageWidth () >= 0 && source->GetImageHeight () >= 0) {
			m_AspectRatio = source->GetImageWidth() / source->GetImageHeight();
		} else {
			m_AspectRatio = 1.0;
		}

		MultiScaleSubImage *si = NULL;
		if (source) {
			dzits = dynamic_cast<DeepZoomImageTileSource *>(source);
			for (guint i = 0; i < dzits->GetSubImageCount (); i++) {
				si = dzits->GetSubImage (i);
				subs->Add (si);
			}
		}

		Invalidate ();

		// Get the first tiles
		int shared_index = -1;
		QTree *shared_cache = (QTree *) g_hash_table_lookup(cache, &shared_index);
		if (!shared_cache)
			g_hash_table_insert (cache, new int(shared_index), (shared_cache = qtree_new ()));

		m_InitLayers = 0;  //the image size is larger than display window size

		if ( abs( frexp (MAX (GetActualWidth(), GetActualHeight()), &m_InitLayers)- 0.5f) <= 1e-6 )
			m_InitLayers --;

		int tile_width = source->GetTileWidth();
		int tile_height = source->GetTileHeight();
		int img_w = source->GetImageWidth();
		int img_h = source->GetImageHeight();

		if ( tile_width ==0 || tile_height == 0 || img_w == 0 || img_h == 0 )
		{
			g_warning(" HandleDzParsed: tile width or height is zero! ^o^ \n");
			return;
		}

		int tilesW = ceil( GetActualWidth() / tile_width );
		int tilesH = ceil( GetActualHeight() / tile_height );

		if ( abs( frexp ( (double)MAX (img_w, img_h), &m_MaxLayers) - 0.5f) <= 1e-6 )
			m_MaxLayers --;

		//init cache tile queue. 2^8=256=tileW
		// min cached tile is  tilesW * tilesH * (m_MaxLayers-8)
		int totalCacheTiles = tilesW * tilesH * m_MaxLayers;
		if ( m_MaxCacheTiles < totalCacheTiles ) {
			m_MaxCacheTiles = totalCacheTiles;
			LOG_MSI(" max cache tiles is [%d].\n", m_MaxCacheTiles );
		}

		int layer = 0;	
		while ( layer <= m_InitLayers ) {
			void *tile = NULL;
			guint64 layers2 = pow2( layer );
			double maxx = MIN( layers2 / tile_width, GetActualWidth()/tile_width );
			double maxy = MIN( layers2 / tile_height, GetActualHeight()/tile_height );

			int i=0, j=0;
			do 
			{
				do 
				{
					if (source->get_tile_func (source, layer, i, j,  &tile) && tile != NULL) {

						QTree *node = NULL;
						if ((node = qtree_insert (shared_cache, layer,i, j))) {
							if ( pushLoadedTileToCache( layer, i, j, node ) ) {
								LoadTile (tile, node);
								LOG_MSI( "init load tile: layer[%d] ---- [%d, %d].\n", layer, i, j );
							}
						}

						g_free(tile);
						tile = NULL;
					}
					j++;
				} while ( j < maxy  );
				i++;
				j =0;
			} while ( i < maxx );

			layer++;
		}

		EmitImageOpenSucceeded();
	}

	void MultiScaleImage::fade_finished(gpointer closure)
	{
		MultiScaleImage *msi = (MultiScaleImage *) closure;
		msi->FadeFinished ();
	}

	void MultiScaleImage::FadeFinished ()
	{
		motion = (motion & ~MOTION_IS_FADING) | MOTION_IS_FINISHED;
		if (!IS_IN_MOTION (motion)) {
			//printf ("FadeFinished emitting MotionFinished\n");
			MotionFinished ();
		}
	}

	void MultiScaleImage::zoom_finished (gpointer closure)
	{
		MultiScaleImage *msi = (MultiScaleImage *) closure;
		msi->ZoomFinished ();
	}

	void MultiScaleImage::ZoomFinished ()
	{
		motion = (motion & ~MOTION_IS_ZOOMING) | MOTION_IS_FINISHED;
		if (!IS_IN_MOTION (motion)) {
			//printf ("ZoomFinished emitting MotionFinished\n");
			MotionFinished ();
		}
	}

	void MultiScaleImage::pan_finished(gpointer closure)
	{
		MultiScaleImage *msi = (MultiScaleImage *) closure;
		msi->PanFinished ();
	}

	void MultiScaleImage::PanFinished ()
	{
		motion = (motion & ~MOTION_IS_PANNING) | MOTION_IS_FINISHED;
		if (!IS_IN_MOTION (motion)) {
			//printf ("PanFinished emitting MotionFinished\n");
			MotionFinished ();
		}
	}

	void MultiScaleImage::tile_opened(gpointer closure)
	{
		BitmapImageContext *ctx = ( BitmapImageContext * ) closure;

		if ( ctx ) {
			ctx->msi->TileOpened (ctx);
		}
	}

	void MultiScaleImage::TileOpened (BitmapImageContext *ctx)
	{
		ProcessTile (ctx);

		m_loaders.Unlink (ctx);
		ctx->image->unref();
		delete ctx;
		ctx = NULL;
		Invalidate ();
	}

	void MultiScaleImage::tile_failed(gpointer closure)
	{
		BitmapImageContext *ctx = (BitmapImageContext *) closure;

		ctx->msi->TileFailed (ctx);
	}

	void MultiScaleImage::TileFailed (BitmapImageContext *ctx)
	{
		LOG_MSI( "caching tile failed. for %s.\n", (char *)ctx->image->GetTileSource() );
		qtree_set_tile (ctx->node, NULL, 0.0);
		m_loaders.Unlink (ctx);
		ctx->image->unref ();
		delete ctx;
		Invalidate ();

		EmitImageFailed ();
	}

	void MultiScaleImage::unLoading ()
	{
		BitmapImageContext *ctx = NULL;

#ifdef MULTI_THREAD_LOAD_TILE
		EnterCriticalSection( &g_LTLock );
#endif

		while (!m_loaders.IsEmpty ()) {
			ctx = static_cast<BitmapImageContext *> (m_loaders.First ());
			m_loaders.Unlink (ctx);
			//ctx->image->Abort ();
			ctx->image->unref ();
			delete ctx;
		}

#ifdef MULTI_THREAD_LOAD_TILE
		LeaveCriticalSection( &g_LTLock );
#endif
	}

	void MultiScaleImage::tile_layer_invalidated( gpointer closure, int level, int tilePosX, int tilePosY, int tileLayer )
	{
		MultiScaleImage *msi = (MultiScaleImage *) closure;

		msi->InvalidateTileLayer ( level, tilePosX, tilePosY, tileLayer );
	}

	void MultiScaleImage::LoadCompleted()
	{
		HandleDzParsed ();
	}

	void MultiScaleImage::LoadFailed()
	{
		EmitImageOpenFailed ();
	}

	Size MultiScaleImage::ComputeActualSize ()
	{
		Size result = Size(m_Width, m_Height);
		Size available;

		available = Size (INFINITY, INFINITY);
		available = ApplySizeConstraints (available);

		// FIXME: this keeps the moon-unit DefaultValue tests passing.
		if (isinf (available.width) && isinf (available.height))
			return Size (0, 0);

		result = MeasureOverrideWithError (available);
		result = ApplySizeConstraints (result);

		return result;
	}

	Size MultiScaleImage::ApplySizeConstraints (const Size &size)
	{
		Size specified ( m_Width, m_Height );
		Size constrained ( m_MinWidth, m_MinHeight );

		constrained = constrained.Max (size);

		if (!isnan (specified.width))
			constrained.width = specified.width;

		if (!isnan (specified.height))
			constrained.height = specified.height;

		constrained = constrained.Min(m_MaxWidth, m_MaxHeight);
		constrained = constrained.Max(m_MinWidth, m_MinHeight);

		//if (GetUseLayoutRounding ()) {
		//	constrained.width = round (constrained.width);
		//	constrained.height = round (constrained.height);
		//}

		return constrained;
	}

	Size MultiScaleImage::MeasureOverrideWithError (Size availableSize)
	{
		double vp_w = GetAnimatedViewportWidth ();
		double vp_ar = GetAspectRatio ();
		Size desired = availableSize;

		if (isinf (desired.width)) {
			if (isinf (desired.height)) {
				desired.height = vp_w / vp_ar;
				desired.width = vp_w;
			} else {
				desired.width = desired.height * vp_ar;
			}
		} else if (isinf (desired.height)) {
			desired.height = desired.width / vp_ar;
		}

		return desired;
	}

	void MultiScaleImage::ProcessTile(BitmapImageContext *ctx)
	{
		double tile_fade = GetTileFade();

#ifdef USE_ANIMATION
		if (!fadein_sb) {
			fadein_sb = CreateStoryboard ();
			fadein_sb->SetManualTargetWithError (this, NULL);
			fadein_sb->SetTargetProperty (fadein_sb, new PropertyPath("(MultiScaleImage.TileFade)"));
			fadein_animation = CreateDoubleAnimation ();
			fadein_animation->SetDuration (Duration (GetSource ()->GetTileBlendTime ()));
			TimelineCollection *tlc = new TimelineCollection ();
			tlc->Add (static_cast<DoubleAnimation*> (fadein_animation));
			fadein_sb->SetChildren (tlc);
			tlc->unref ();
			fadein_sb->AddHandler (Storyboard::CompletedEvent, fade_finished, this);
	#if DEBUG
			fadein_sb->SetName ("Multiscale Fade-In");
	#endif
		} else {
			fadein_sb->PauseWithError (NULL);
			motion &= ~MOTION_IS_FADING;
		}

		tile_fade = GetTileFade ();
		//LOG_MSI ("animating Fade from %f to %f\n\n", tile_fade, tile_fade + 0.9);
		fadein_animation->SetFrom (tile_fade);
		fadein_animation->SetTo (tile_fade + 0.9);

		if (fadein_sb->BeginWithError (NULL))
			motion |= MOTION_IS_FADING;
#endif

		UpdateIdleStatus ();
		
		//LOG_MSI ("caching ProcessTile: %s! \n", (char *)ctx->image->GetTileSource() );

		qtree_set_tile (ctx->node, ctx->image, tile_fade + 0.9);
	}

#ifdef MULTI_THREAD_LOAD_TILE
	void MultiScaleImage::UpdateStatus()
	{
	}
#else
	void MultiScaleImage::UpdateStatus()
	{
		//if( fadein_animation )
		//if( zoom_animation )
		//if( pan_animation )
		//OnPropertyChanged();
		//OnCollectionChanged();
		//OnCollectionItemChanged();

		UpdateIdleStatus();
		DynamicLoading();
		//UpdateLoading();
	}
#endif

	void MultiScaleImage::UpdateIdleStatus ()
	{
		//SetIsIdle (m_loaders.Length () == 0 && !IS_IN_MOTION (motion));
		SetIsIdle ( !IS_IN_MOTION (motion));
	}

#ifdef MULTI_THREAD_LOAD_TILE
	void MultiScaleImage::UpdateLoading()
	{
		//EnterCriticalSection( &g_LTLock );

		while ( m_loaders.Length() > 0 )
		{
			List::Node * pNode = m_loaders.Index(0);
			if ( pNode ) {
				BitmapImageContext *pCtx = (BitmapImageContext *) pNode;
				if ( pCtx->image ) {
					pCtx->image->loaderComplete();
				}
				tile_opened( pNode );
			}
		}

		//LeaveCriticalSection( &g_LTLock );
	}
#else
	void MultiScaleImage::UpdateLoading()
	{
		//if the loader is max, then remove the head,
		while ( m_loaders.Length() > 0 )
		{
			tile_opened( m_loaders.Index(0) );
		}
	}
#endif

	void MultiScaleImage::Render ()
	{
		MultiScaleTileSource *source = GetSource ();
		DeepZoomImageTileSource *dzits = NULL;

		//LOG_MSI ("MSI::Render\n");

		if (!source) {
			LOG_MSI ("no sources set, nothing to render\n");
			UpdateIdleStatus ();
			return;
		}

		dzits = dynamic_cast<DeepZoomImageTileSource *> (source);

		bool is_collection = dzits && dzits->IsCollection () && GetSubImages ();

		if (source->GetImageWidth () < 0 && !is_collection) {
			LOG_MSI ("nothing to render so far...\n");
			if (dzits != NULL)
				dzits->loadRes();

			UpdateIdleStatus ();
			return;
		}

	#if DEBUG
		if (!source->get_tile_func) {
			g_warning ("no get_tile_func set\n");
			return;
		}
	#endif

		if (is_collection)
			RenderCollection (m_pContext, m_pRegion);
		else
			RenderSingle (m_pContext, m_pRegion);
	}

	void MultiScaleImage::RenderCollection (Context *ctx, Region *region)
	{
		MultiScaleTileSource *source = GetSource ();
		DeepZoomImageTileSource *dzits;
		double msi_w = GetActualWidth ();
		double msi_h = GetActualHeight ();
		double msi_ar = GetAspectRatio();
		Point pt = GetAnimatedViewportOrigin();
		double msivp_ox = pt.x;
		double msivp_oy = pt.y;
		double msivp_w = GetAnimatedViewportWidth();
		double blur_factor = GetBlurFactor ();
		double fade = GetTileFade ();
		int blur_offset;
		int max_level;

		LOG_MSI ("\nMSI::RenderCollection\n");

		if (!source) {
			g_warning ("RenderCollection called for a non deepzoom tile source. this should not happen");
			return;
		}

		dzits = dynamic_cast<DeepZoomImageTileSource *> (source);

		if (!dzits->IsParsed ())
			return;

		max_level = dzits->GetMaxLevel ();

		if (msi_w <= 0.0 || msi_h <= 0.0 || !blur_factor_is_valid (blur_factor))
			return; // invisible widget, nothing to render

		cairo_t *cr = ctx->Push (Context::Cairo ());

		blur_offset = blur_factor_get_offset (blur_factor);

		Rect viewport = Rect (msivp_ox, msivp_oy, msivp_w, msivp_w/msi_ar);

		MultiScaleSubImageCollection *subs = GetSubImages ();
		if (!subimages_sorted) {
			subs->ResortByZIndex ();
			subimages_sorted = true;
		}

		// using the "-1" index for the shared cache
		int shared_index = -1;
		QTree *shared_cache = (QTree *) g_hash_table_lookup (cache, &shared_index);
		if (!shared_cache)
			g_hash_table_insert (cache, new int(shared_index), (shared_cache = qtree_new ()));

		int subs_count = subs->GetCount ();

		for (int i = 0; i < subs_count; i++) {
			MultiScaleSubImage *sub_image = (MultiScaleSubImage *) subs->z_sorted->pdata[i];
			DeepZoomImageTileSource *sub_dzits = (DeepZoomImageTileSource *) sub_image->source;

			int index = sub_image->GetId();
			QTree *subimage_cache = (QTree *) g_hash_table_lookup (cache, &index);
			if (!subimage_cache)
				g_hash_table_insert (cache, new int(index), (subimage_cache = qtree_new ()));

			double subvp_ox = sub_image->GetViewportOrigin()->x;
			double subvp_oy = sub_image->GetViewportOrigin()->y;
			double subvp_w = sub_image->GetViewportWidth();
			double sub_w = sub_image->source->GetImageWidth ();
			double sub_h = sub_image->source->GetImageHeight ();
			double sub_ar = sub_image->GetAspectRatio();

			// expressing the subimage viewport in main viewport coordinates.
			Rect sub_vp = Rect (-subvp_ox / subvp_w, -subvp_oy / subvp_w, 1.0/subvp_w, 1.0/(sub_ar * subvp_w));

			// render only if the subimage viewport intersects with this viewport
			if (!sub_vp.IntersectsWith (viewport))
				continue;

			LOG_MSI ("Intersects with main viewport...rendering\n");

			int layers;
			if (frexp (MAX (sub_w, sub_h), &layers) == 0.5)
				layers--;

			int optimal_layer;

			frexp (msi_w / (subvp_w * msivp_w * MIN (1.0, sub_ar)), &optimal_layer);
			LOG_MSI ("number of layers: %d; optimal layer: %d; BlurFactor: %.2f; adjustment: %d\n",
				layers, optimal_layer, blur_factor, blur_offset);

			optimal_layer = MIN (optimal_layer + blur_offset, layers);

			int to_layer = -1;
			int from_layer = optimal_layer;
			while (from_layer >= 0) {
				bool parsed = (from_layer > max_level && sub_dzits->IsParsed ());
				int tile_width = parsed ? sub_image->source->GetTileWidth () : dzits->GetTileWidth ();
				int tile_height = parsed ? sub_image->source->GetTileHeight () : dzits->GetTileHeight ();
				guint64 from_layer2 = pow2 (from_layer);
				guint64 layers2 = pow2 (layers - from_layer);
				double v_scale = (double) layers2 * sub_vp.width / sub_w;
				double v_tile_w = tile_width * v_scale;
				double v_tile_h = tile_height * v_scale;
				double minx = (MAX (msivp_ox, sub_vp.x) - sub_vp.x) / v_tile_w;
				double maxx = MIN (msivp_ox + msivp_w, sub_vp.x + sub_vp.width) - sub_vp.x;
				double miny = (MAX (msivp_oy, sub_vp.y) - sub_vp.y) / v_tile_h;
				double maxy = MIN (msivp_oy + msivp_w / msi_ar, sub_vp.y + sub_vp.width / sub_ar) - sub_vp.y;
				bool blending = false;
				int count = 0;
				int found = 0;

				//LOG_MSI ("virtual tile size at layer %d; %fx%f\n", from_layer, v_tile_w, v_tile_h);

				for (guint64 i = (guint64) minx; i < from_layer2 && i * v_tile_w < maxx; i++) {
					for (guint64 j = (guint64) miny; j < from_layer2 && j * v_tile_h < maxy; j++) {
						QTreeTile *tile;

						count++;

						if (from_layer > max_level)
							tile = qtree_lookup_tile (subimage_cache, from_layer, i, j);
						else
							tile = qtree_lookup_tile (shared_cache, from_layer,
							morton_x (sub_image->n) * from_layer2 / tile_width,
							morton_y (sub_image->n) * from_layer2 / tile_height);

						if (tile && tile->image) {
							if (tile->opacity > GetTileFade ())
								blending = true;

							found++;
						}
					}
				}

				if (found > 0 && to_layer < from_layer)
					to_layer = from_layer;

				if (found == count && (!blending || from_layer == 0))
					break;

				from_layer--;
			}

			// render loop
			LOG_MSI ("rendering layers from %d to %d\n", from_layer, to_layer);

			if (from_layer >= 0) {
				cairo_save (cr);
				cairo_rectangle (cr, 0, 0, msi_w, msi_h);
				cairo_clip (cr);
				cairo_scale (cr, msi_w / msivp_w, msi_w / msivp_w); //scale to widget

				cairo_translate (cr, 
					-msivp_ox + sub_vp.x,
					-msivp_oy + sub_vp.y);

				cairo_scale (cr, 
					sub_vp.width/sub_w, 
					sub_vp.width/sub_w);

				cairo_rectangle (cr, 0, 0, sub_w, sub_h);
				cairo_clip (cr);

				if (IS_TRANSLUCENT (sub_image->GetOpacity ()))
					cairo_push_group (cr);

				int layer_to_render = from_layer;
				while (layer_to_render <= to_layer) {
					bool parsed = (layer_to_render > max_level && sub_dzits->IsParsed ());
					int tile_width = parsed ? sub_image->source->GetTileWidth () : dzits->GetTileWidth ();
					int tile_height = parsed ? sub_image->source->GetTileHeight () : dzits->GetTileHeight ();
					guint64 render_layer2 = pow2 (layer_to_render);
					guint64 layers2 = pow2 (layers - layer_to_render);
					double v_scale = (double) layers2 * sub_vp.width / sub_w;
					double v_tile_w = tile_width * v_scale;
					double v_tile_h = tile_height * v_scale;
					double minx = (MAX (msivp_ox, sub_vp.x) - sub_vp.x) / v_tile_w;
					double maxx = MIN (msivp_ox + msivp_w, sub_vp.x + sub_vp.width) - sub_vp.x;
					double miny = (MAX (msivp_oy, sub_vp.y) - sub_vp.y) / v_tile_h;
					double maxy = MIN (msivp_oy + msivp_w / msi_ar, sub_vp.y + sub_vp.width / sub_ar) - sub_vp.y;

					for (guint64 i = (guint64) minx; i < render_layer2 && i * v_tile_w < maxx; i++) {
						for (guint64 j = (guint64) miny; j < render_layer2 && j * v_tile_h < maxy; j++) {
							bool shared_tile = false;
							QTreeTile *tile;

							if (layer_to_render > max_level)
								tile = qtree_lookup_tile (subimage_cache, layer_to_render, i, j);
							else {
								// Check in the shared levels
								shared_tile = true;

								tile = qtree_lookup_tile (shared_cache, layer_to_render,
									morton_x (sub_image->n) * render_layer2 / tile_width,
									morton_y (sub_image->n) * render_layer2 / tile_height);
							}

							if (!tile || !tile->image)
								continue;

							LOG_MSI ("rendering subimage %d %d %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT "\n", sub_image->id, layer_to_render, i, j);
							cairo_save (cr);

							cairo_scale (cr, layers2, layers2);

							cairo_translate (cr, i * tile_width, j * tile_height);

							if (shared_tile) {
								cairo_translate (cr,
									(int)(-morton_x(sub_image->n) * render_layer2) % tile_width,
									(int)(-morton_y(sub_image->n) * render_layer2) % tile_height);
							}

							cairo_set_source_surface (cr, tile->image->GetImageSurface (), 0, 0);

							double combined = 1.0;

							if (tile->opacity > fade) 
								combined = MIN (1.0 - tile->opacity + fade, 1.0);

							if (IS_TRANSLUCENT (combined))
								cairo_paint_with_alpha (cr, combined);
							else
								cairo_paint (cr);

							cairo_restore (cr);
						}
					}
					layer_to_render++;
				}

				if (IS_TRANSLUCENT (sub_image->GetOpacity ())) {
					cairo_pattern_t *data = cairo_pop_group (cr);
					if (cairo_pattern_status (data) == CAIRO_STATUS_SUCCESS) {
						cairo_set_source (cr, data);
						cairo_paint_with_alpha (cr, sub_image->GetOpacity ());
					}
					cairo_pattern_destroy (data);
				}

				cairo_restore (cr);
			}

			// Download the next set of tiles..
			while (from_layer < optimal_layer) {
				from_layer ++;

				// if the subimage is unparsed, trigger the download
				if (from_layer > max_level && !sub_dzits->IsLoaded ()) {
					sub_dzits->loadRes();
					break;
				}

				bool parsed = (from_layer > max_level && sub_dzits->IsParsed ());
				int tile_width = parsed ? sub_image->source->GetTileWidth () : dzits->GetTileWidth ();
				int tile_height = parsed ? sub_image->source->GetTileHeight () : dzits->GetTileHeight ();
				guint64 layers2 = pow2 (layers - from_layer);
				double v_scale = (double) layers2 * sub_vp.width / sub_w;
				double v_tile_w = tile_width * v_scale;
				double v_tile_h = tile_height * v_scale;
				double minx = (MAX (msivp_ox, sub_vp.x) - sub_vp.x) / v_tile_w;
				double maxx = MIN (msivp_ox + msivp_w, sub_vp.x + sub_vp.width) - sub_vp.x;
				double miny = (MAX (msivp_oy, sub_vp.y) - sub_vp.y) / v_tile_h;
				double maxy = MIN (msivp_oy + msivp_w / msi_ar, sub_vp.y + sub_vp.width / sub_ar) - sub_vp.y;
				MultiScaleTileSource *tile_source;
				QTree *tile_cache, *node;
				guint64 x, y;
				void *tile;

				for (int i = (int) minx; i * v_tile_w < maxx; i++) {
					for (int j = (int) miny; j * v_tile_h < maxy; j++) {

						if (from_layer <= max_level) {
							guint64 from_layer2 = pow2 (from_layer);
							x = morton_x (sub_image->n) * from_layer2 / tile_width;
							y = morton_y (sub_image->n) * from_layer2 / tile_height;
							tile_cache = shared_cache;
							tile_source = dzits;
						} else {
							tile_source = sub_image->source;
							tile_cache = subimage_cache;
							x = i;
							y = j;
						}

						if (!(node = qtree_insert (tile_cache, from_layer, x, y)))
							continue;

						if (!qtree_has_tile (node)) {
							tile = NULL;
							if (dzits->get_tile_func (tile_source, from_layer, x, y, &tile))
								LoadTile (tile, node);

							g_free(tile);
						}
					}
				}
			}
		}

		ctx->Pop ();
	}

	void MultiScaleImage::RenderSingle (Context *ctx, Region *region)
	{
		MultiScaleTileSource *source = GetSource();
		double msi_w = GetActualWidth();
		double msi_h = GetActualHeight();
		double msi_ar = GetAspectRatio();
		double im_w = source->GetImageWidth();
		double im_h = source->GetImageHeight();
		int tile_width = source->GetTileWidth();
		int tile_height = source->GetTileHeight();
		Point pt = GetAnimatedViewportOrigin();
		double vp_ox = pt.x;
		double vp_oy = pt.y;
		double vp_w = GetAnimatedViewportWidth ();
		double blur_factor = GetBlurFactor();
		double fade = 1.0;//GetTileFade ();
		int optimal_layer = 0;
		int blur_offset = 0;
		int layers = 0;

		if (msi_w <= 0.0 || msi_h <= 0.0 || !blur_factor_is_valid (blur_factor))
			return; // invisible widget, nothing to render

		cairo_t *cr = ctx->Push (Context::Cairo ());

 	    //cairo_surface_t* hdc_surface = cairo_win32_surface_create(m_hDC);
 		//cairo_t* cr = cairo_create(hdc_surface);

		blur_offset = blur_factor_get_offset (blur_factor);

		// number of layers in the MSI, aka the lowest powerof2 that's bigger than width and height
		if (frexp (MAX (im_w, im_h), &layers) == 0.5)
			layers --;

		// optimal layer for this... aka "best viewed at"
		double fr = 0.0;

		if ((fr = frexp (msi_w / (vp_w * MIN (1.0, msi_ar)), &optimal_layer)) == 0.5)
			optimal_layer--;

		//LOG_MSI ("number of layers: %d; optimal layer: %d; BlurFactor: %.2f; adjustment: %d; fr = %.4f\n", layers, optimal_layer, blur_factor, blur_offset, fr);

		optimal_layer = MIN (optimal_layer + blur_offset, layers);

		// We have to figure all the layers that we'll have to render:
		// - from_layer is the highest COMPLETE layer that we can display (all tiles are
		//   there and blended (except for level 0, where it might not be blended yet))
		// - to_layer is the highest PARTIAL layer that we can display (contains at least
		//   1 tiles partially blended)

		int to_layer = -1;
		int from_layer = optimal_layer;

		// using the "-1" index for the single image case
		int index = -1;
		QTree *subimage_cache = (QTree *) g_hash_table_lookup (cache, &index);
		if (!subimage_cache)
			return;
		//	g_hash_table_insert (cache, new int(index), (subimage_cache = qtree_new ()));

		while (from_layer >= 0) {
			guint64 layers2 = pow2 (layers - from_layer);
			guint64 from_layer2 = pow2 (from_layer);
			double v_scale = (double) layers2 / im_w;
			double v_tile_w = tile_width * v_scale;
			double v_tile_h = tile_height * v_scale;
			double minx = MAX (0, (vp_ox / v_tile_w));
			double maxx = MIN (vp_ox + vp_w, 1.0);
			double miny = MAX (0, (vp_oy / v_tile_h));
			double maxy = MIN (vp_oy + vp_w / msi_w * msi_h, 1.0 / msi_ar);
			bool blending = false;
			int count = 0;
			int found = 0;

			// This double loop iterate over the displayed part of the image and find all (i,j) being top-left corners of tiles
			for (guint64 i = (guint64) minx; i < from_layer2 && i * v_tile_w < maxx; i++) {
				for (guint64 j = (guint64) miny; j < from_layer2 && j * v_tile_h < maxy; j++) {
					QTreeTile *tile = qtree_lookup_tile (subimage_cache, from_layer, i, j);

					count++;

					if (tile && tile->image) {
						if (tile->opacity > fade)
							blending = true;

						found++;
					}
				}
			}

			if (found > 0 && to_layer < from_layer)
				to_layer = from_layer;

			if (found == count && (!blending || from_layer == 0))
				break;

			from_layer --;
		}

		//render here
		//cairo_push_group (cr);

		cairo_save (cr);
		cairo_matrix_t render_xform;
		cairo_matrix_init_identity (&render_xform);
		cairo_matrix_scale (&render_xform, msi_w / vp_w, msi_w / vp_w);
 		cairo_matrix_translate (&render_xform, -vp_ox, -vp_oy);
		cairo_matrix_scale (&render_xform, 1.0 / im_w, 1.0 / im_w);

		/*
		* here we want to clip to the bounds of the image to ensure we don't
		* have scaling artifacts on the edges but the image potentially has bounds
		* larger that cairo can handle right now so we transform the image
		* bounds to the viewport coordinate space and do intersection and clipping
		* there to work around the cairo coordinate limitations.
		*/
		Rect vp_bounds (0, 0, msi_w, msi_h);
		Rect im_bounds (0, 0, im_w, im_h);
		im_bounds = im_bounds.Transform (&render_xform);
		Rect render_region = vp_bounds.Intersection (im_bounds);
		render_region.Draw (cr);
		cairo_clip (cr);

		cairo_transform (cr, &render_xform);

		//LOG_MSI ("rendering layers from %d to %d\n", from_layer, to_layer);

		int layer_to_render = MAX (0, from_layer);
		while (layer_to_render <= to_layer) {
			guint64 layers2 = pow2 (layers - layer_to_render);
			guint64 render_layer2 = pow2 (layer_to_render);
			double v_scale = (double) layers2 / im_w;
			double v_tile_w = tile_width * v_scale;
			double v_tile_h = tile_height * v_scale;
			double minx = MAX (0, (vp_ox / v_tile_w));
			double maxx = MIN (vp_ox + vp_w, 1.0);
			double miny = MAX (0, (vp_oy / v_tile_h));
			double maxy = MIN (vp_oy + vp_w / msi_w * msi_h, 1.0 / msi_ar);

			for (guint64 i = (guint64) minx; i < render_layer2 && i * v_tile_w < maxx; i++) {
				for (guint64 j = (guint64) miny; j < render_layer2 && j * v_tile_h < maxy; j++) {
					QTreeTile *tile = qtree_lookup_tile (subimage_cache, layer_to_render, i, j);

					if (!tile || !tile->image || !tile->image->GetImageSurface() )
						continue;

					//LOG_MSI (" rendering %d %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT "\n", layer_to_render, i, j);
					cairo_save (cr);

					// scale to image size
					cairo_scale (cr, layers2, layers2);

					cairo_translate (cr, i * tile_width, j * tile_height);

					cairo_set_source_surface (cr, tile->image->GetImageSurface (), 0, 0);

					double combined = 1.0;

 					if (tile->opacity > fade)
 						combined = MIN (1.0 - tile->opacity + fade, 1.0);
 
 					if (IS_TRANSLUCENT (combined))
 						cairo_paint_with_alpha (cr, combined);
 					else
						cairo_paint (cr);   // mem leak

					cairo_restore (cr);
				}
			}

			layer_to_render++;
		}

		cairo_restore (cr);
		//cairo_pop_group_to_source (cr);

		ctx->Pop ();

#if 0
		if ( !CanLoadMoreTiles () )
			return;

		// Download the next set of tiles...
		while (from_layer < optimal_layer) {
			from_layer++;

			guint64 layers2 = pow2 (layers - from_layer);
			double v_scale = (double) layers2 / im_w;
			double v_tile_w = tile_width * v_scale;
			double v_tile_h = tile_height * v_scale;
			double minx = MAX (0, (vp_ox / v_tile_w));
			double maxx = MIN (vp_ox + vp_w, 1.0);
			double miny = MAX (0, (vp_oy / v_tile_h));
			double maxy = MIN (vp_oy + vp_w / msi_w * msi_h, 1.0 / msi_ar);
			QTree *node;

			for (int i = (int) minx; i * v_tile_w < maxx; i++) {

				if (!CanLoadMoreTiles ())
					return;

				for (int j = (int) miny; j * v_tile_h < maxy; j++) {

					if (!CanLoadMoreTiles ())
						return;

					if (!(node = qtree_insert (subimage_cache, from_layer, i, j)))
						continue;

					if (!qtree_has_tile (node)) {
						void *tile = NULL;

						if (source->get_tile_func (source, from_layer, i, j, &tile))
							LoadTile (tile, node);
						else
							qtree_set_tile (node, NULL, 0.0);

						//delete tile;
						g_free(tile);
					}
				}
			}
		}

	#ifdef MULTI_THREAD_LOAD_TILE
			EnterCriticalSection( &g_LTLock );
			if ( m_loaders.Length() > 0 )
			{
				PostThreadMessage( g_LTThreadID, MSG_LT_LOAD, NULL, NULL );
			}
			LeaveCriticalSection( &g_LTLock );
	#else
			UpdateLoading();
	#endif

#endif

//#define LOAD_TILE_DYNAMIC
#ifdef LOAD_TILE_DYNAMIC

#if 0
		//buffer the next stage tiles
		from_layer = 0;
		while( from_layer < optimal_layer ) {
			from_layer++;
			
			guint64 layers2 = pow2( layers - from_layer );
			double v_scale = (double) layers2/im_w;
			double v_tile_w = tile_width * v_scale;
			double v_tile_h = tile_height * v_scale;
			double minx = MAX ( 0, vp_ox - v_tile_w*max_buffer_tile_num );
			double maxx = MIN ( vp_ox + vp_w+v_tile_w*max_buffer_tile_num, 1.0);
			double miny = MAX ( 0, vp_oy - v_tile_h*max_buffer_tile_num );
			double maxy = MIN ( vp_oy + vp_w / msi_w * msi_h +v_tile_h*max_buffer_tile_num, 1.0 / msi_ar);
			QTree *node = NULL;

			//calc the region in current layer
			double regionMinX = MAX( 0.0, minx/v_tile_w );
			double regionMaxX = maxx/v_tile_w;
			double regionMinY = MAX( 0.0, miny/v_tile_h );
			double regionMaxY = maxy/v_tile_h;

			if ( from_layer == optimal_layer )
			{
				LOG_MSI(" buffer valid range is layer[%d] -> x: [%.1f - %.1f], y: [%.1f - %.1f].\n", from_layer, regionMinX, regionMaxX, regionMinY, regionMaxY );
			}			

			for ( int i = 0; i * v_tile_w < maxx; i++ ) {
				for ( int j = 0; j * v_tile_h < maxy; j++ ) {
					if ( i >= regionMinX && i <= regionMaxX && j >= regionMinY && j <= regionMaxY) {

						if ( !(node = qtree_insert( subimage_cache, from_layer, i, j)) ) {
							continue;
						}

						if ( !qtree_has_tile(node) ) {
							void * tile = NULL;
							if ( source->get_tile_func( source, from_layer, i, j, &tile)) {
								LoadTile( tile, node );
							}
							else {
								qtree_set_tile( node, NULL, 0.0 );
							}

							g_free(tile);
						}

					}
				}
			}
		}

	#ifdef MULTI_THREAD_LOAD_TILE
		EnterCriticalSection( &g_LTLock );
		if ( m_loaders.Length() > 0 )
		{
			PostThreadMessage( g_LTThreadID, MSG_LT_LOAD, NULL, NULL );
		}
		LeaveCriticalSection( &g_LTLock );
	#else
		UpdateLoading();
	#endif

#endif

		//unload the tiles outside the current rendering buffer region
		//the init layers[0, m_InitLayers] not unload, when
		from_layer = layers;  //&& (optimal_layer != layers)
		while( from_layer>m_InitLayers ) {
			
			guint64 layers2 = pow2( layers-from_layer );
			double v_scale = (double) layers2 / im_w;
			double v_tile_w = tile_width * v_scale;
			double v_tile_h = tile_height * v_scale;
			double minx = MAX ( 0, vp_ox - v_tile_w*max_buffer_tile_num );
			double maxx = MIN ( vp_ox + vp_w+v_tile_w*max_buffer_tile_num, 1.0);
			double miny = MAX ( 0, vp_oy - v_tile_h*max_buffer_tile_num );
			double maxy = MIN ( vp_oy + vp_w / msi_w * msi_h +v_tile_h*max_buffer_tile_num, 1.0 / msi_ar);
			QTree *node = NULL;

			double regionMinX = MAX( 0.0, minx/v_tile_w );
			double regionMaxX = maxx/v_tile_w;
			double regionMinY = MAX( 0.0, miny/v_tile_h );
			double regionMaxY = maxy/v_tile_h;

			for ( int i=0; i * v_tile_w < 1.0;  i++ ) {
				for ( int j=0; j * v_tile_h < 1.0;  j++ ) {

					if ( i < regionMinX || i > regionMaxX || j < regionMinY || j > regionMaxY || from_layer>optimal_layer ) {

						if ( !(node = qtree_insert( subimage_cache, from_layer, i, j)) ) {
							continue;
						}

						if ( qtree_has_tile(node) )  {
							LOG_MSI (" remove layer[%d]: %d %d.\n", from_layer, i, j );
							qtree_remove_at( subimage_cache, from_layer, i, j, 0 );
						}

					}
				}
			}

			from_layer--;
		}
#endif
	}

	void MultiScaleImage::uri_source_changed(gpointer closure)
	{
		((MultiScaleImage *) closure)->UriSourceChanged ();
	}

	void MultiScaleImage::UriSourceChanged ()
	{
		DeepZoomImageTileSource *dzits = NULL;
		MultiScaleTileSource *source;

		// Abort all m_loaders
		unLoading();

		if ((source = GetSource ())) {
			if (source) {
				dzits = dynamic_cast<DeepZoomImageTileSource *> (source);
				dzits->loadRes();
			} else {
				EmitImageOpenSucceeded ();
				EmitMotionFinished ();
			}
		}

		// Reset the viewport
		//ClearValue (AnimatedViewportOriginProperty, true);
		//ClearValue (AnimatedViewportWidthProperty, true);
		//ClearValue (MultiScaleImage::ViewportOriginProperty, true);
		//ClearValue (MultiScaleImage::ViewportWidthProperty, true);

		// Invalidate the whole cache
		if (cache) {
			g_hash_table_destroy (cache);
			cache = g_hash_table_new_full (g_int_hash, g_int_equal, int_free, (GDestroyNotify) qtree_destroy);
		}

		// Reset the subimages
		GetSubImages()->Clear ();

		Invalidate ();
	}

	void MultiScaleImage::OnPropertyChanged( PropertyChanged _propertyChangedID, void *args )
	{
		if ( _propertyChangedID == AnimatedViewportOriginProperty ||
			_propertyChangedID == AnimatedViewportWidthProperty) {
				if (GetUseSprings () )//&& HasHandlers (ViewportChangedEvent))
					Emit (ViewportChangedEvent);

				Invalidate ();
		} else if ( _propertyChangedID == AspectRatioProperty) {
			InvalidateMeasure ();
			Invalidate ();
		} else if ( _propertyChangedID == BlurFactorProperty) {
			Invalidate ();
		} else if (_propertyChangedID == ViewportOriginProperty) {
			Point *origin = (Point *) args;
			pan_target = Point (origin->x, origin->y);
			AnimateViewportOrigin (origin);
		} else if (_propertyChangedID == ViewportWidthProperty) {
			zoom_target = *(double *)args;
			AnimateViewportWidth (zoom_target);
		} else if (_propertyChangedID == TileFadeProperty) {
			// There's 2 options here,
			//  - loop all the tiles, update their opacity, and only invalidate a subregion
			//  - Invalidate all, and compute the new opacity on the tiles that needs to be rendered.
			// Both options are unfortunately quite expensive :(
			Invalidate ();
		} else if (_propertyChangedID == SourceProperty) {
			// disconnect event handlers from the old source
			//if (args->GetOldValue ())
			//	DisconnectSourceEvents (args->GetOldValue ()->AsMultiScaleTileSource ());

			// connect event handlers to the new source
			//if (args->GetNewValue ())
			//	ConnectSourceEvents (args->GetNewValue ()->AsMultiScaleTileSource ());

			UriSourceChanged ();
		} else if (_propertyChangedID == UseSpringsProperty) {
#ifdef USE_ANIMATION
			if (!args->GetNewValue()->AsBool ()) {
				if (zoom_sb) {
					double endpoint = zoom_target;
					zoom_sb->StopWithError (NULL);
					zoom_target = NAN;
					SetAnimatedViewportWidth (endpoint);
				}

				if (pan_sb) {
					Point endpoint = pan_target;
					pan_sb->StopWithError (NULL);
					pan_target = Point (NAN, NAN);
					SetAnimatedViewportOrigin (&endpoint);
				}

				Invalidate ();
			}
#endif
		}
	}

	void MultiScaleImage::OnCollectionChanged( CollectionChangedEvent CCEventID, MultiScaleSubImage *subimage, int count )
	{
		switch (CCEventID) 
		{
			case CollectionChangedActionReplace:
				// Disconnect events from the replaced item				
				//DisconnectSubImageEvents (subimage);
				// now fall thru to Add
			case CollectionChangedActionAdd:
				// Connect to events on the new item				
				//ConnectSubImageEvents (subimage);
				break;
			case CollectionChangedActionRemove:
				// Disconnect events from the removed item				
				//DisconnectSubImageEvents (subimage);
				break;
			case CollectionChangedActionClearing:
				// Disconnect events from all subitems				
				for (int i = 0; i < count; i++) {
					//subimage = col->GetValueAt (i)->AsMultiScaleSubImage ();
					//DisconnectSubImageEvents (subimage);
				}
				break;
			case CollectionChangedActionCleared:
				// no-op
				break;
			default:
				break;
		}

		subimages_sorted = false;
		Invalidate ();
	}

	void MultiScaleImage::OnCollectionItemChanged( PropertyChanged _propertyChangedID )
	{
		if (_propertyChangedID == ViewportWidthProperty)
			Invalidate ();
		if (_propertyChangedID == ViewportOriginProperty)
			Invalidate ();
		if (_propertyChangedID == ZIndexProperty) {
			subimages_sorted = false;
			Invalidate ();
		}
	}

	void MultiScaleImage::EmitImageFailed ()
	{
		LOG_MSI ("MSI::Emitting image failed\n");

		Emit (ImageFailedEvent);
	}

	void MultiScaleImage::EmitImageOpenFailed ()
	{
		LOG_MSI ("MSI::Emitting image open failed\n");

		Emit (ImageOpenFailedEvent );
	}

	void MultiScaleImage::EmitImageOpenSucceeded ()
	{
		LOG_MSI ("\nMSI::Emitting open suceeded\n");

#ifdef USE_THREADS_POOL
		createThreadsPool();
#endif

#ifdef MULTI_THREAD_LOAD_TILE
		g_hLoadTileThread = (HANDLE) _beginthreadex( NULL, 0, dynamicLoadTileThread, NULL, 0, &g_LTThreadID );

		if ( g_hLoadTileThread != NULL && g_hLoadTileEvent )
		{
			int timeOut = 500;
#ifdef _DEBUG
			timeOut = 2000;
#endif
			DWORD ret = WaitForSingleObject( g_hLoadTileEvent, timeOut );
			if ( ret == WAIT_TIMEOUT )
			{
				g_warning(" load tile thread: waiting event timeout.\n ");
				return;
			}
		}

		EnterCriticalSection( &g_LTLock );

		if ( m_loaders.Length() > 0 )
		{
			PostThreadMessage( g_LTThreadID, MSG_LT_LOAD, NULL, NULL );
		}

		LeaveCriticalSection( &g_LTLock );

#else
		Emit (ImageOpenSucceededEvent);
		UpdateLoading();
#endif		

		FullInvalidate (true);
	}

	void MultiScaleImage::EmitMotionFinished ()
	{
		LOG_MSI ("Emitting MotionFinished\n");

		motion &= ~MOTION_IS_FINISHED;		
		Emit (MotionFinishedEvent);
	}

	void MultiScaleImage::MotionFinished ()
	{
		//SetIsIdle (m_loaders.Length () == 0);
		EmitMotionFinished ();
	}

	static void motion_finished (MultiScaleImage *msi)
	{
		msi->MotionFinished ();
	}

	Point * MultiScaleImage::GetPanAnimationEndPoint ()
	{
#ifdef USE_ANIMATION
		return pan_animation->GetKeyFrames ()->GetValueAt (0)->AsSplinePointKeyFrame ()->GetValue ();
#else
		return NULL;
#endif
	}

	void MultiScaleImage::SetPanAnimationEndPoint (Point value)
	{
#ifdef USE_ANIMATION
		pan_animation->GetKeyFrames ()->GetValueAt (0)->AsSplinePointKeyFrame ()->SetValue (value);
#endif
	}

	double MultiScaleImage::GetZoomAnimationEndPoint ()
	{
#ifdef USE_ANIMATION
		return zoom_animation->GetKeyFrames ()->GetValueAt (0)->AsSplineDoubleKeyFrame ()->GetValue ();
#else
		return 0.0;
#endif
	}

	void MultiScaleImage::SetZoomAnimationEndPoint (double value)
	{
#ifdef USE_ANIMATION
		zoom_animation->GetKeyFrames ()->GetValueAt (0)->AsSplineDoubleKeyFrame ()->SetValue (value);
#endif
	}

	void MultiScaleImage::AnimateViewportWidth (double width)
	{
		if (!GetUseSprings ()) {
			if (motion == 0) {
				//printf ("SetAnimatedViewportWidth(): queueing MotionFinished\n");
#ifdef USE_ANIMATION
				AddTickCall ((TickCallHandler) motion_finished);
#endif
				motion = MOTION_IS_FINISHED;
			}

			SetAnimatedViewportWidth (width);
			return;
		}

#ifdef USE_ANIMATION
		if (!zoom_sb) {
			zoom_sb = CreateStoryboard ();
			zoom_sb->SetManualTargetWithError (this, NULL);
			zoom_sb->SetTargetProperty (zoom_sb, new PropertyPath ("(MultiScaleImage.AnimatedViewportWidth)"));
			zoom_sb->AddHandler (Storyboard::CompletedEvent, zoom_finished, this);
			zoom_animation = CreateDoubleAnimationUsingKeyFrames ();
			zoom_animation->SetDuration (Duration::FromSeconds (4));
			zoom_animation->SetKeyFrames (DOPtr<DoubleKeyFrameCollection> (MoonUnmanagedFactory::CreateDoubleKeyFrameCollection ()));
			DOPtr<SplineDoubleKeyFrame> keyframe (CreateSplineDoubleKeyFrame ());
			keyframe->SetKeySpline (DOPtr<KeySpline> (new KeySpline (.05, .5, 0, 1.0)));
			keyframe->SetKeyTime (KeyTime::FromPercent (1.0));
			zoom_animation->GetKeyFrames ()->Add (static_cast<SplineDoubleKeyFrame*>(keyframe));

			DOPtr<TimelineCollection> tlc (new TimelineCollection ());
			tlc->Add (static_cast<DoubleAnimationUsingKeyFrames*>(zoom_animation));
			zoom_sb->SetChildren(tlc);
	#if DEBUG
			zoom_sb->SetName ("Multiscale Zoom");
	#endif
		} else {
			zoom_sb->PauseWithError (NULL);
			motion &= ~MOTION_IS_ZOOMING;
		}
#endif

		LOG_MSI ("animating zoom from %f to %f\n\n", GetAnimatedViewportWidth (), width);

		SetZoomAnimationEndPoint (width);

#ifdef USE_ANIMATION
		if (zoom_sb->BeginWithError (NULL))
			motion |= MOTION_IS_ZOOMING;
#endif

		UpdateIdleStatus ();
	}

	void MultiScaleImage::AnimateViewportOrigin (Point *origin)
	{
		if (!GetUseSprings ()) {
			if (motion == 0) {
				//printf ("SetAnimatedViewportOrigin(): queueing MotionFinished\n");
#ifdef USE_ANIMATION
				AddTickCall ((TickCallHandler) motion_finished);
#endif
				motion = MOTION_IS_FINISHED;
			}

			SetAnimatedViewportOrigin (origin);
			return;
		}

#ifdef USE_ANIMATION
		if (!pan_sb) {
			pan_sb = CreateStoryboard ();
			pan_sb->SetManualTargetWithError (this, NULL);
			pan_sb->SetTargetProperty (pan_sb, new PropertyPath ("(MultiScaleImage.AnimatedViewportOrigin)"));
			pan_sb->AddHandler (Storyboard::CompletedEvent, pan_finished, this);
			pan_animation = CreatePointAnimationUsingKeyFrames ();
			pan_animation->SetDuration (Duration::FromSeconds (4));
			pan_animation->SetKeyFrames (DOPtr<PointKeyFrameCollection> (MoonUnmanagedFactory::CreatePointKeyFrameCollection ()));
			SplinePointKeyFrame *keyframe = CreateSplinePointKeyFrame ();
			keyframe->SetKeySpline (DOPtr<KeySpline> (new KeySpline (.05, .5, 0, 1.0)));
			keyframe->SetKeyTime (KeyTime::FromPercent (1.0));
			pan_animation->GetKeyFrames ()->Add (keyframe);
			keyframe->unref ();

			TimelineCollection *tlc = new TimelineCollection ();
			tlc->Add (static_cast<PointAnimationUsingKeyFrames*> (pan_animation));
			pan_sb->SetChildren(tlc);
			tlc->unref ();
#if DEBUG
			pan_sb->SetName ("Multiscale Pan");
#endif
		} else {
			pan_sb->PauseWithError (NULL);
			motion &= ~MOTION_IS_PANNING;
		}
#endif

		SetPanAnimationEndPoint (*origin);

#ifdef USE_ANIMATION
		if (pan_sb->BeginWithError (NULL))
			motion |= MOTION_IS_PANNING;
#endif

		UpdateIdleStatus ();
	}

	void MultiScaleImage::SetAnimatedViewportOrigin(Point *origin)
	{
		EnterCriticalSection( &g_LTLock );
		m_AnimatedViewportOrigin.x = origin->x;
		m_AnimatedViewportOrigin.y = origin->y;
		LeaveCriticalSection( &g_LTLock );
	}

	Point MultiScaleImage::GetAnimatedViewportOrigin()
	{
		Point pt;
		EnterCriticalSection( &g_LTLock );
		pt.x = m_AnimatedViewportOrigin.x;
		pt.y = m_AnimatedViewportOrigin.y;
		LeaveCriticalSection( &g_LTLock );

		return pt;
	}

	void MultiScaleImage::SetAnimatedViewportWidth(double width)
	{
		EnterCriticalSection( &g_LTLock );
		m_AnimatedViewportWidth = width;
		LeaveCriticalSection( &g_LTLock );
	}

	double MultiScaleImage::GetAnimatedViewportWidth()
	{
		double ret = 0.0;

		EnterCriticalSection( &g_LTLock );
		ret = m_AnimatedViewportWidth;
		LeaveCriticalSection( &g_LTLock );

		return ret;
	}

	void MultiScaleImage::SetIsIdle (bool value)
	{
		m_IsIdle = value;
	}

	int MultiScaleImage::LogicalToElementX (int x, int y)
	{
		return (int) LogicalToElementPoint (Point (x, y)).x;
	}

	int MultiScaleImage::LogicalToElementY (int x, int y)
	{
		return (int) LogicalToElementPoint (Point (x, y)).y;
	}

	MultiScaleSubImage * MultiScaleImage::GetIthSubImage (int index)
	{
		MultiScaleSubImageCollection *sub_images = GetSubImages ();

		if (sub_images == NULL)
			return NULL;

		MultiScaleSubImage *value  = (MultiScaleSubImage *) sub_images->GetValueAt (index);

		if (value == NULL)
			return NULL;

		return value;
	}

	int MultiScaleImage::GetSubImageCount ()
	{
		MultiScaleSubImageCollection *sub_images = GetSubImages();

		if (sub_images == NULL)
			return 0;
		return sub_images->GetCount ();
	}

	void MultiScaleImage::InvalidateTileLayer (int level, int tilePositionX, int tilePositionY, int tileLayer)
	{
		if (GetSource ()) {
			g_warning ("calling InvalidateTileLayer on DeepZoom Images makes no sense. \n");
			return;
		}

		unLoading();

		int index = -1;
		QTree *subimage_cache = (QTree *) g_hash_table_lookup (cache, &index);
		if (subimage_cache)
			qtree_remove_at (subimage_cache, level, tilePositionX, tilePositionY, 0);

		Invalidate ();
	}

	//
	// Queues the invalidate for the current m_pRegion, performs any 
	// updates to the RenderTransform (optional) and queues a 
	// new redraw with the new bounding box
	//
	void MultiScaleImage::FullInvalidate (bool rendertransform)
	{
		Invalidate ();
		if (rendertransform) {
			UpdateTransform ();
			UpdateProjection ();
		}
		UpdateBounds (true /* force an invalidate here, even if the bounds don't change */);
	}

	void MultiScaleImage::Invalidate ()
	{
		Invalidate( GetBounds() );
	}

	void MultiScaleImage::Invalidate (Rect r)
	{
		if (!GetRenderVisible() || IS_INVISIBLE(total_opacity))
			return;

		//LOG_MSI ("Requesting invalidate for object at %f %f - %f %f\n", r.x, r.y, r.w, r.h );

		//if (IsAttached ()) {
		//	GetDeployment ()->GetSurface ()->AddDirtyElement (this, DirtyInvalidate);

			InvalidateBitmapCache ();

			if (RenderToIntermediate ())
				dirty_region->Union (GetSubtreeBounds ());
			else
				dirty_region->Union (r);

			//GetTimeManager()->NeedRedraw ();

			Emit (InvalidatedEvent);
		//}

			//remove some unused tiles.
	}

	void MultiScaleImage::UpdateBounds (bool force_redraw)
	{
#if 0
		//InvalidateMeasure ();
		//InvalidateArrange ();

		if (IsAttached ())
			GetDeployment ()->GetSurface ()->AddDirtyElement (this, DirtyBounds);

		force_invalidate_of_new_bounds |= force_redraw;
#endif
	}

	void MultiScaleImage::UpdateTransform ()
	{
		//if (IsAttached ()) {
		//	GetDeployment ()->GetSurface ()->AddDirtyElement (this, DirtyLocalTransform);
		//}
	}

	void MultiScaleImage::UpdateProjection ()
	{
		//if (IsAttached ()) {
		//	GetDeployment ()->GetSurface ()->AddDirtyElement (this, DirtyLocalProjection);
		//}
	}

	bool MultiScaleImage::RenderToIntermediate ()
	{
		return true;
	#if 0
		if (GetEffect ()) return TRUE;
		if (GetProjection ()) return TRUE;
		if (GetCacheMode ()) return TRUE;
		if (flags & UIElement::RENDER_PROJECTION) return TRUE;

		return FALSE;
	#endif
	}

	void MultiScaleImage::InvalidateBitmapCache ()
	{
#if 0
		if (bitmap_cache_size) {
			if (!GetDeployment ()->IsShuttingDown ())
				GetDeployment ()->GetSurface ()->RemoveGPUSurface (bitmap_cache_size);
			bitmap_cache.Release ();
			bitmap_cache_size = 0;
		}
#endif
	}

	bool MultiScaleImage::Emit (int event_id)
	{
		switch ( event_id )
		{
		case ImageOpenSucceededEvent:
			break;
		case InvalidatedEvent:
			break;
		default:
			break;
		}
		return true;
	}

	void MultiScaleImage::InvalidateMeasure ()
	{
		//dirty_flags |= DirtyMeasure;
		//PropagateFlagUp (DIRTY_MEASURE_HINT);
		//GetTimeManager()->NeedRedraw();
	}

	void MultiScaleImage::SetCairoSurface(int width, int height,unsigned char * data)
	{
		if (data)
		{
			//CairoSurface * pTarget = new CairoSurface(width, height,data);
			//m_pContext = new CairoContext( pTarget );
			//pTarget->unref();
			m_pTarget = new CairoSurface(width, height,data);
			m_pContext = new CairoContext( m_pTarget );
		}
		else
		{
		}
	}

	void MultiScaleImage::SetCairoData(unsigned char* data)
	{
		if (NULL!=m_pTarget && NULL != data)
		{
			m_pTarget->SetData(data);
		}
	}

	void MultiScaleImage::DynamicLoading()
	{
		MultiScaleTileSource *source = GetSource();
		double msi_w = GetActualWidth();
		double msi_h = GetActualHeight();
		double msi_ar = GetAspectRatio();
		double im_w = source->GetImageWidth();
		double im_h = source->GetImageHeight();
		int tile_width = source->GetTileWidth();
		int tile_height = source->GetTileHeight();
		Point pt = GetAnimatedViewportOrigin();
		double vp_ox = pt.x;
		double vp_oy = pt.y;
		double vp_w = GetAnimatedViewportWidth();
		double blur_factor = GetBlurFactor();
		double fade = 1.0;//GetTileFade ();
		int optimal_layer = 0;
		int blur_offset = 0;
		int layers = 0;

		if (msi_w <= 0.0 || msi_h <= 0.0 || !blur_factor_is_valid (blur_factor))
			return; // invisible widget, nothing to render

		blur_offset = blur_factor_get_offset (blur_factor);

		// number of layers in the MSI, aka the lowest powerof2 that's bigger than width and height
		if (frexp (MAX (im_w, im_h), &layers) == 0.5)
			layers --;

		// optimal layer for this... aka "best viewed at"
		double fr = 0.0;

		if ((fr = frexp (msi_w / (vp_w * MIN (1.0, msi_ar)), &optimal_layer)) == 0.5)
			optimal_layer--;

		//LOG_MSI ("number of layers: %d; optimal layer: %d; BlurFactor: %.2f; adjustment: %d; fr = %.4f\n", layers, optimal_layer, blur_factor, blur_offset, fr);

		optimal_layer = MIN (optimal_layer + blur_offset, layers);

		int to_layer = -1;
		int from_layer = optimal_layer;

		// using the "-1" index for the single image case
		int index = -1;
		QTree *subimage_cache = (QTree *) g_hash_table_lookup (cache, &index);
		if (!subimage_cache)
			g_hash_table_insert (cache, new int(index), (subimage_cache = qtree_new ()));

		updateCacheQueueTimeStamp( -1, -1, -1, 1, true );

		from_layer = MIN( (optimal_layer+1), layers );  //&& (optimal_layer != layers)
		while( from_layer>=m_InitLayers ) {

			guint64 layers2 = pow2( layers-from_layer );
			double v_scale = (double) layers2 / im_w;
			double v_tile_w = tile_width * v_scale;
			double v_tile_h = tile_height * v_scale;
			double minx = MAX ( 0, vp_ox );
			double maxx = MIN ( vp_ox + vp_w, 1.0);
			double miny = MAX ( 0, vp_oy );
			double maxy = MIN ( vp_oy + vp_w / msi_w * msi_h, 1.0 / msi_ar);
			QTree *node = NULL;

			int regionMinX = floor( MAX( 0.0, minx/v_tile_w ) );
			int regionMaxX = ceil( maxx/v_tile_w );
			int regionMinY = floor( MAX( 0.0, miny/v_tile_h ) );
			int regionMaxY = ceil( maxy/v_tile_h );

			//LOG_MSI(" --> update time stamp: layer[%d], X : [%d, %d], Y : [%d, %d].\n", from_layer, regionMinX, regionMaxX, regionMinY, regionMaxY );

			for ( int i=0; i * v_tile_w < 1.0;  i++ ) {
				for ( int j=0; j * v_tile_h < 1.0;  j++ ) {

					if ( i >= regionMinX && i <= regionMaxX && j >= regionMinY && j <= regionMaxY ) {  //|| from_layer>optimal_layer ) {

						if ( !(node = qtree_insert( subimage_cache, from_layer, i, j)) ) {
							continue;
						}

						//if in the valid render range, then set the time stamp to 0.
						if ( qtree_has_tile(node) )  {
							updateCacheQueueTimeStamp( from_layer, i, j, -2, false );
						}

					}
				}
			}

			from_layer--;
		}

		if ( !CanLoadMoreTiles () )
			return;

		// Download the next set of tiles...
		while (from_layer < optimal_layer) {
			from_layer++;

			guint64 layers2 = pow2 (layers - from_layer);
			double v_scale = (double) layers2 / im_w;
			double v_tile_w = tile_width * v_scale;
			double v_tile_h = tile_height * v_scale;
			double minx = MAX (0, (vp_ox / v_tile_w));
			double maxx = MIN (vp_ox + vp_w, 1.0);
			double miny = MAX (0, (vp_oy / v_tile_h));
			double maxy = MIN (vp_oy + vp_w / msi_w * msi_h, 1.0 / msi_ar);
			QTree *node;

			for (int i = (int) minx; i * v_tile_w < maxx; i++) {

				if (!CanLoadMoreTiles ())
					return;

				for (int j = (int) miny; j * v_tile_h < maxy; j++) {

					if (!CanLoadMoreTiles ())
						return;

					if (!(node = qtree_insert (subimage_cache, from_layer, i, j)))
						continue;

					if (!qtree_has_tile (node)) {
						void *tile = NULL;

						if (source->get_tile_func (source, from_layer, i, j, &tile)) {
							if ( pushLoadedTileToCache( from_layer, i, j, node ) ) {
								LoadTile (tile, node);
							} else {
								g_free(tile);
								break;
							}
						}
						else
							qtree_set_tile (node, NULL, 0.0);

						g_free(tile);
					}
				}
			}
		}

	#ifdef USE_THREADS_POOL
		if ( m_loaders.Length() > 0 ) {
			commitWorks();
			UpdateLoading();
		}
	#else
		//PostThreadMessage( g_LTThreadID, MSG_LT_LOAD, NULL, NULL );
		UpdateLoading();
	#endif
	}

	//unload tile in Least Recently Used
	//the removed tile is not in the current rendering range, not will be used in next level
	bool MultiScaleImage::unloadTileByLRU()
	{
		ULONG maxTimeStamp = 0;
		int idx = -1;
		LoadedTile *pTile = NULL;
		for ( int i = m_pCacheTileQueue->size()-1; i >= 0; i-- ) {
			pTile = m_pCacheTileQueue->at(i);
			if ( pTile && pTile->_timeStamp > maxTimeStamp ) {
				maxTimeStamp = pTile->_timeStamp;
				idx = i;
			}
		}

		if ( idx > 0 ) {
			LoadedTile *pTile = m_pCacheTileQueue->at(idx);
			LOG_MSI( " --> unloadTileByLRU tile[%d : (%d, %d)], max time stamp is %ld.\n", pTile->_layerIndex, pTile->_row, pTile->_col, maxTimeStamp );
			removeLoadedTileFromCache( idx );
			return true;
		} else {
			g_warning( " --> unload Tile failed by LRU. please enlarge the cache queue size.\n" );
			return false;
		}
	}

	bool MultiScaleImage::pushLoadedTileToCache( int layerIdx, int _row, int _col, QTree *pNode )
	{
		if ( m_pCacheTileQueue->size() >= m_MaxCacheTiles && unloadTileByLRU() == false ) {
			return false;
		}

		//insert element in order: layer first, row first, col.
		int idx = -1;
		for ( int i=0; i < m_pCacheTileQueue->size(); i++ ) {
			LoadedTile *pTile = m_pCacheTileQueue->at(i);
			if ( pTile->_layerIndex < layerIdx ) {
				continue;
			} else if ( pTile->_layerIndex > layerIdx ) {
				idx = i;
				break;
			} else {
				if ( _row < pTile->_row ) {
					idx = i;
					break;
				} else if ( _row == pTile->_row ) {
					if ( _col < pTile->_col ) {
						idx = i;
						break;
					} else if ( _col == pTile->_col ) {
						idx = -2;
						break;
					}
				}
			}
		}

		LoadedTile * pLoadedTile = NULL;
		if ( idx > -2 ) {
			pLoadedTile = new LoadedTile();
			pLoadedTile->_layerIndex = layerIdx;
			pLoadedTile->_row = _row;
			pLoadedTile->_col = _col;
			pLoadedTile->pNode = pNode;
		}

		if ( idx == -1 ) {
			if ( m_pCacheTileQueue->size() > 0) {
				LoadedTile *pTile = m_pCacheTileQueue->back();
				LOG_MSI( " --> push loaded tile[%d : (%d, %d)] after: tile[%d : (%d, %d)].\n", pLoadedTile->_layerIndex, pLoadedTile->_row, pLoadedTile->_col, pTile->_layerIndex, pTile->_row, pTile->_col );
			}
			m_pCacheTileQueue->push_back( pLoadedTile );
		} else if ( idx > 0 ){
			if ( m_pCacheTileQueue->size() > 0) {
				LoadedTile *pTile = m_pCacheTileQueue->at(idx);
				LOG_MSI( " --> insert loaded tile[%d : (%d, %d)] before: tile[%d : (%d, %d)].\n", pLoadedTile->_layerIndex, pLoadedTile->_row, pLoadedTile->_col, pTile->_layerIndex, pTile->_row, pTile->_col );
			}
			m_pCacheTileQueue->insert( m_pCacheTileQueue->begin() + idx, pLoadedTile );
		}

		if ( idx == -2 ) {
			g_warning( " --> push the same loaded tile[%d : (%d, %d)] again.", layerIdx, _row, _col );
			return false;
		} else {
			return true;
		}
	}

	void MultiScaleImage::removeLoadedTileFromCache( int layerIdx, int _row, int _col, QTree *pNode )
	{
		if ( m_pCacheTileQueue == NULL || m_pCacheTileQueue->size() < m_MaxCacheTiles ) {
			return;
		}

		int idx = getTileIndexInCacheQueue( layerIdx, _row, _col );
		if ( idx > 0 ) {
			removeLoadedTileFromCache( idx );
		}
	}

	void MultiScaleImage::removeLoadedTileFromCache( int _idx )
	{
		if ( m_pCacheTileQueue == NULL || _idx < 0 || _idx >= m_pCacheTileQueue->size() ) {
			return;
		}

		int index = -1;
		QTree *subimage_cache = (QTree *) g_hash_table_lookup (cache, &index);
		LoadedTile *pTile = m_pCacheTileQueue->at(_idx);

		if ( qtree_has_tile( pTile->pNode) )  {
			LOG_MSI (" --> remove tile from cache queue, layer[%d]: %d %d.\n", pTile->_layerIndex, pTile->_row, pTile->_col );
			qtree_remove_at( subimage_cache, pTile->_layerIndex, pTile->_row, pTile->_col, 0 );
		}

		delete pTile;
		m_pCacheTileQueue->erase( m_pCacheTileQueue->begin()+_idx );
	}

	void MultiScaleImage::updateCacheQueueTimeStamp( int layerIdx, int _row, int _col, int _val, bool updateAll )
	{
		LoadedTile *pTile = NULL;

		if ( updateAll ) {
			//LOG_MSI("\n --> begin update all tiles time stamp.\n" );
			for ( int i=0; i<m_pCacheTileQueue->size(); i++ ) {
				pTile = m_pCacheTileQueue->at(i);
				if ( pTile->_layerIndex >= m_InitLayers ){
					pTile->_timeStamp += _val;
					//LOG_MSI("   --> tile[%d : (%d, %d)] time stamp is %ld.\n", pTile->_layerIndex, pTile->_row, pTile->_col, pTile->_timeStamp );
				}
			}
			//LOG_MSI(" --> end update all tiles time stamp.\n\n" );
		} else {
			pTile = findTileInCacheQueue( layerIdx, _row, _col );
			if ( pTile ) {
				if ( _val < -1 ) {
					pTile->_timeStamp = 0;
				} else {
					pTile->_timeStamp += _val;
				}
				//LOG_MSI(" --> tile[%d : (%d, %d)] time stamp is %ld.\n", pTile->_layerIndex, pTile->_row, pTile->_col, pTile->_timeStamp );
			}
		}		
	}

	LoadedTile * MultiScaleImage::findTileInCacheQueue( int layerIdx, int _row, int _col )
	{
		// the tiles in layer [0, m_InitLayers) are always cached in the queue.
		if ( layerIdx < m_InitLayers ) {
			return NULL;
		}

		int idx = getTileIndexInCacheQueue( layerIdx, _row, _col );
		if ( idx > 0 ) {
			return m_pCacheTileQueue->at(idx);
		}

		return NULL;
	}

	int MultiScaleImage::getTileIndexInCacheQueue( int layerIdx, int _row, int _col )
	{
		if ( layerIdx < 0 ) {
			return -1;
		}

		LoadedTile *pTile = NULL;
		for ( int i=0; i < m_pCacheTileQueue->size(); i++ ) {
			pTile = m_pCacheTileQueue->at(i);
			if ( pTile && pTile->_layerIndex == layerIdx && pTile->_row == _row && pTile->_col == _col ) {
				return i;
			}
		}

		return -1;
	}

#ifdef MULTI_THREAD_LOAD_TILE
	unsigned int WINAPI loadTileThread( void * lpParam )
	{
		MSG msg;
		ZeroMemory( &msg, sizeof(msg) );

		PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE );
		SetEvent( g_hLoadTileEvent );

		while ( GetMessage( &msg, NULL, 0, 0) ) {

			switch( msg.message ) {

			case MSG_LT_LOAD:
				if ( g_pMSI && g_pMSI->GetLoaders()->Length() > 0 ) {
					//g_pMSI->UpdateLoading();
					g_pMSI->DynamicLoading();
				}
				SetEvent( g_hLoadTileEvent );
				break;
			default:
				DispatchMessage( &msg );
				break;
			}

		}

		return 0;
	}

	const int TIME_PER_FRAME = 30; 
	unsigned int WINAPI dynamicLoadTileThread( void * lpParam )
	{
		LARGE_INTEGER _liBeginTick = {0};
		LARGE_INTEGER _liEndTick = {0};
		LARGE_INTEGER _liFreqTick = {0};

		SetEvent( g_hLoadTileEvent );

		HANDLE hSleepEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
		XSleep( 1, hSleepEvent );

		QueryPerformanceFrequency( &_liFreqTick );

		bool bQuit = false;

		while( !bQuit )
		{
			if ( g_pMSI == NULL ) {
				continue;
			}

			EnterCriticalSection( &g_LTLock );
			bQuit = g_bQuitThread;
			LeaveCriticalSection( &g_LTLock );
			if ( bQuit ) {
				break;
			}

			QueryPerformanceCounter( &_liBeginTick );

			if ( g_pMSI ) {
				g_pMSI->DynamicLoading();
			}

			QueryPerformanceCounter( &_liEndTick );
			double _spendTime = (_liEndTick.QuadPart - _liBeginTick.QuadPart)*1000.0f/_liFreqTick.QuadPart;
			if ( _spendTime >= 0 && _spendTime < TIME_PER_FRAME ) {
				XSleep( (DWORD)(TIME_PER_FRAME - _spendTime), hSleepEvent );
			}

		}

		CloseHandle( hSleepEvent );
		hSleepEvent = NULL;

		return 0;
	}

	void XSleep( DWORD dwDelay, HANDLE hEvent )
	{
		if ( dwDelay <= 0 || hEvent == NULL )
		{
			return;
		}
		//hEvent = NULL, will sleep 0 ms

		MMRESULT hTimer = timeSetEvent( dwDelay,1, (LPTIMECALLBACK)hEvent, NULL, TIME_ONESHOT | TIME_CALLBACK_EVENT_SET);	
		//MsgWaitForMultipleObjectsEx( 1, &hEvent, INFINITE, QS_ALLINPUT, 0 );  //is OK too, and hEvent must not be NULL
		WaitForSingleObject( hEvent, INFINITE );
		timeKillEvent( hTimer );
	}

#endif
}
